/**
 *       Copyright (c) 2012 Lukas Zaruba
 *
 *   This file is part of Robot Playground.
 *
 *   Robot Playground is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Robot Playground is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Robot Playground. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zarubsys.robotplayground.gui.running;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import net.zarubsys.robotplayground.core.program.IProgram;
import net.zarubsys.robotplayground.core.program.IProgramConfiguration;
import net.zarubsys.robotplayground.core.program.IProgramFinishedListener;
import net.zarubsys.robotplayground.core.session.RunningSession;
import net.zarubsys.robotplayground.core.session.Session;
import net.zarubsys.robotplayground.gui.running.logging.ConsoleLogAppender;
import net.zarubsys.robotplayground.gui.running.logging.SessionConsole;
import net.zarubsys.robotplayground.gui.session.editor.SessionEditor;
import net.zarubsys.robotplayground.gui.session.providers.IRunListener;
import net.zarubsys.robotplayground.gui.session.providers.ISystemObjectGUIProvider;
import net.zarubsys.robotplayground.gui.session.providers.SystemObjectGUIProviderClass;
import net.zarubsys.robotplayground.gui.session.providers.SystemObjectGUIProvidersHelper;
import net.zarubsys.robotplayground.gui.session.utils.ISystemObjectAction;
import net.zarubsys.robotplayground.gui.session.utils.SessionGUIUtils;

import org.apache.log4j.Logger;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IOConsoleOutputStream;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.handlers.HandlerUtil;

/**
 * RunCommandHandler
 *
 * @author Lukas Zaruba, lukas.zaruba@gmail.com
 */
public class RunCommandHandler extends AbstractHandler {
	
	private static final Logger LOG = Logger.getLogger(RunCommandHandler.class);

	private static final String LOG_EXTENSION = "log";
	private static final String LOG_DATE_PATTERN = "yyyy-MM-dd_HH-mm-ss";
	
	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		SessionEditor editor = getEditor(event);
		Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
		
		boolean valid = editor.validate();
		if (!valid) {
			MessageDialog.open(MessageDialog.WARNING, 
					shell, "Cannot run session", 
					"Session cannot run until it is valid. Please check it for errors", SWT.NONE);
			return null;
		}
		
		offerSavingOfUnsavedChanges(editor, shell);
		
		Session session = editor.getSession();
		
		final String logPath = getLogPath(editor);
		final RunningSession runningSession;
		try {
			runningSession = session.prepareRunningSession(logPath);
		} catch (Exception e) {
			MessageDialog.openError(shell, "Error while preparing a session", e.getMessage());
			return null;
		}
		
		initializeConsole(session, logPath, runningSession);
		registerRunListeners(session, runningSession);
		
		try {
			runningSession.run();
		} catch (Exception e) {
			MessageDialog.openError(shell, "Error while starting a session", e.getMessage());
			return null;
		}
		
		return null;	
	}

	private void registerRunListeners(Session session, final RunningSession runningSession) {
		SessionGUIUtils.executeOnAllSystemObjects(session, new ISystemObjectAction() {
			
			@Override
			public void run(Session session, String id, int indexInClass, SystemObjectGUIProviderClass klass) {
				ISystemObjectGUIProvider provider = SystemObjectGUIProvidersHelper.getInstance().getProvider(klass, id);
				Assert.isNotNull(provider, "Provider must not be null!");
				if (!(provider instanceof IRunListener)) return;
				IRunListener runListener = (IRunListener) provider;
				runListener.notifyRuns(runningSession, indexInClass);
			}
			
		});
	}

	private String getLogPath(SessionEditor editor) {
		DateFormat df = new SimpleDateFormat(LOG_DATE_PATTERN);
		String timestamp = df.format(new Date());
		IPath sessionPath = editor.getSessionPath();
		IPath logPathTorso = Path.fromPortableString(sessionPath.removeFileExtension().toFile().toString() + "_" + timestamp);
		int i =-1;
		IPath finalPath;
		while ((finalPath = getLogPathInternal(logPathTorso, i)).toFile().exists()) {
			i++;
		}
		return finalPath.toPortableString();
	}

	private IPath getLogPathInternal(IPath logPathTorso, int i) {
		if (i < 0) {
			return logPathTorso.addFileExtension(LOG_EXTENSION);
		}
		return logPathTorso.append("_" + i).addFileExtension(LOG_EXTENSION);
	}

	private SessionEditor getEditor(ExecutionEvent event) {
		IEditorPart activeEditor = HandlerUtil.getActiveEditor(event);
		if (!(activeEditor instanceof SessionEditor)) {
			throw new IllegalArgumentException("Expecting active editor to be an instance of " + SessionEditor.class.getName() + ". Received " + activeEditor);
		}
		SessionEditor editor = (SessionEditor) activeEditor;
		return editor;
	}

	private void offerSavingOfUnsavedChanges(SessionEditor editor, Shell shell) {
		if (editor.isDirty() && MessageDialog.openConfirm(shell, 
				"Session is not saved", "Do you want to save your changes before running the session?")) {
			editor.doSave(new NullProgressMonitor());
		}
	}

	private void initializeConsole(Session session, final String logPath, RunningSession runningSession) {
		MessageConsole console = addNewConsole(session, runningSession);
		initializeConsoleStreams(logPath, runningSession, console);
		showConsole(console);
	}

	private MessageConsole addNewConsole(Session session, RunningSession runningSession) {
		MessageConsole console = new SessionConsole("Session '" + session.getName() + "'", runningSession);
		ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[] {console});
		return console;
	}

	private void initializeConsoleStreams(final String logPath, RunningSession runningSession, MessageConsole console) {
		IOConsoleOutputStream os = console.newOutputStream();
		os.setActivateOnWrite(false);
		ConsoleLogAppender.getInstance().addNewOutputStream(logPath, os);
		
		runningSession.addProgramFinishedListener(new IProgramFinishedListener() {
				
			@Override
			public void notifyFinished(IProgram<IProgramConfiguration> program) {
				// ignore
			}
			
			@Override
			public void notifyFinishedAll() {
				ConsoleLogAppender.getInstance().removeOutputStream(logPath);
			}
			
		});
	}

	private void showConsole(MessageConsole console) {
		try {
			PlatformUI.getWorkbench().showPerspective(RunPerspective.ID, PlatformUI.getWorkbench().getActiveWorkbenchWindow());
			ConsolePlugin.getDefault().getConsoleManager().showConsoleView(console);
		} catch (WorkbenchException e) {
			LOG.error("Error while activating perspective " + RunPerspective.ID, e);
			throw new RuntimeException(e);
		}
	}

}

